package auctionsimulation.agent;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import auctionsimulation.communication.command.BidCommand;
import auctionsimulation.communication.command.BuyProductCommand;
import auctionsimulation.communication.command.SearchAuctionsCommand;
import auctionsimulation.communication.response.AuctionSetResponse;
import auctionsimulation.communication.response.ErrorResponse;
import auctionsimulation.communication.response.ErrorResponseException;
import auctionsimulation.communication.response.NotSuccessfullResponse;
import auctionsimulation.communication.response.OKResponse;
import auctionsimulation.communication.response.Response;
import auctionsimulation.helper.AuctionHelper;
import auctionsimulation.market.Auction;
import auctionsimulation.market.DoubleAuction;
import auctionsimulation.market.DutchAuction;
import auctionsimulation.market.ReverseEnglishAuction;

public class BuyAgent extends Agent {

	private static final long serialVersionUID = -8411250415077713657L;

	private Socket sock = null;
	private ObjectInputStream commandInput = null;
	private ObjectOutputStream commandOutput = null;
	private double minPrice, maxPrice;
	protected boolean dutch, reverse, dbl;
	private HashMap<String, Long> requireTemplate;
	private long auctionCheckInterval;

	public BuyAgent(String configFileName) {
		super(configFileName);
	}

	@Override
	public void run() {
		this.init();
		Auction toBuyOrBid = null;
		ArrayList<Auction> relevantAuctions = new ArrayList<Auction>();
		ArrayList<Auction> bestAuctions;
		Random r = new Random();
		while (true) {
			try {
				Thread.sleep(r.nextInt((int)this.auctionCheckInterval));
			} catch (InterruptedException e) {
			}
			relevantAuctions = this.searchForAuctions();
			if (relevantAuctions.size() < 1) {
				continue;
			} else if (relevantAuctions.size() == 1) {
				toBuyOrBid = relevantAuctions.get(0);
			} else {
				bestAuctions = new ArrayList<Auction>();
				toBuyOrBid = relevantAuctions.get(0);
				for (Auction auction : relevantAuctions) {
					if (auction.getCurrentPrice() < toBuyOrBid
							.getCurrentPrice()) {
						toBuyOrBid = auction;
					}
				}
				for (Auction auction : relevantAuctions) {
					if (auction.getCurrentPrice() == toBuyOrBid
							.getCurrentPrice()) {
						bestAuctions.add(auction);
					}
				}
				toBuyOrBid = bestAuctions.get(r.nextInt(bestAuctions.size()));
			}
			if (toBuyOrBid == null) {
				continue;
			}
			if (toBuyOrBid instanceof ReverseEnglishAuction
					|| toBuyOrBid instanceof DutchAuction) {
				try {
					commandOutput
							.writeObject(new BuyProductCommand(toBuyOrBid));
					Response answer = (Response) commandInput.readObject();
					if (answer instanceof OKResponse) {
						System.out.println("Bought product successfull!");
						break;
					} else if (answer instanceof NotSuccessfullResponse) {
						System.out
								.println("Buying product was not successfull!");
						continue;
					} else if (answer instanceof ErrorResponse) {
						throw new ErrorResponseException(answer.getMessage());
					}
				} catch (IOException e) {
					AuctionHelper
							.error("was not able to send or receive BuyProductCommand and Response to CommandStream",
									this, e);
				} catch (ClassNotFoundException e) {
					AuctionHelper
							.error("classtype of send or receive of BuyProductCommand and Response was not found",
									this, e);
				}
			} else if (toBuyOrBid instanceof DoubleAuction) {
				try {
					commandOutput.writeObject(new BidCommand(toBuyOrBid, this,
							this.maxPrice));
					Response answer = (Response) commandInput.readObject();
					if (answer instanceof OKResponse) {
						System.out
								.println("BuyAgent bid for double auction successfully!");
						break;
					} else if (answer instanceof NotSuccessfullResponse) {
						System.out
								.println("Buying product was not successfull!");
						continue;
					} else if (answer instanceof ErrorResponse) {
						throw new ErrorResponseException(answer.getMessage());
					}
				} catch (IOException e) {
					AuctionHelper
							.error("was not able to send or receive BidCommand and Response to CommandStream",
									this, e);
				} catch (ClassNotFoundException e) {
					AuctionHelper
							.error("classtype of send or receive of BidCommand and Response was not found",
									this, e);
				}
			}
			break;
		}
		try {
			commandOutput.close();
			commandInput.close();
			sock.close();
		} catch (IOException e) {
			System.err.println("Error in BuyAgent: ");
			e.printStackTrace();
		}
	}

	private void init() {
		String host = this.behavierProperties.getProperty("host");
		int port = Integer
				.parseInt(this.behavierProperties.getProperty("port"));
		if (host == null)
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: property host not found");
		try {
			sock = new Socket(host, port);
		} catch (UnknownHostException e) {
			AuctionHelper.error("host " + host + " with port " + port
					+ "not found", this, e);
		} catch (IOException e) {
			AuctionHelper.error("was not able to create socket", this, e);
		}
		try {
			commandOutput = new ObjectOutputStream(sock.getOutputStream());
		} catch (IOException e) {
			AuctionHelper.error("was not able to open ObjectOutputStream",
					this, e);
		}
		try {
			commandInput = new ObjectInputStream(sock.getInputStream());
		} catch (IOException e) {
			AuctionHelper.error("was not able to open objectInputStream", this,
					e);
		}

		try {
			this.minPrice = Double.parseDouble(this.behavierProperties
					.getProperty("minPrice"));
		} catch (NumberFormatException | NullPointerException e) {
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: minPrice property not found or invalid in resource file "
							+ this.configFilePath);
		}

		try {
			this.maxPrice = Double.parseDouble(this.behavierProperties
					.getProperty("maxPrice"));
		} catch (NumberFormatException | NullPointerException e) {
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: maxPrice property not found or invalid in resource file "
							+ this.configFilePath);
		}

		try {
			this.dutch = Boolean.parseBoolean(this.behavierProperties
					.getProperty("dutch"));
		} catch (NullPointerException e) {
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: dutch property not found or invalid in resource file "
							+ this.configFilePath);
		}

		try {
			this.reverse = Boolean.parseBoolean(this.behavierProperties
					.getProperty("reverse"));
		} catch (NullPointerException e) {
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: reverse property not found or invalid in resource file "
							+ this.configFilePath);
		}

		try {
			this.dbl = Boolean.parseBoolean(this.behavierProperties
					.getProperty("double"));
		} catch (NullPointerException e) {
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: double property not found or invalid in resource file "
							+ this.configFilePath);
		}

		this.requireTemplate = new HashMap<String, Long>();
		try {
			String temp = this.behavierProperties.getProperty("template");
			String[] tempprops = temp.split(",");
			for (String property : tempprops) {
				this.requireTemplate.put(property.split(":")[0],
						Long.parseLong(property.split(":")[1]));
			}
		} catch (Exception e) {
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: template property not found or invalid in resource file "
							+ this.configFilePath);
		}

		try {
			this.auctionCheckInterval = Long.parseLong(this.behavierProperties
					.getProperty("auctioncheckinterval"));
		} catch (NumberFormatException | NullPointerException e) {
			throw new PropertyNotFoundOrInvalidException(
					"Error creating agent: auctioncheckinterval property not found or invalid in resource file "
							+ this.configFilePath);
		}
	}

	private ArrayList<Auction> searchForAuctions() {
		ArrayList<Auction> result = new ArrayList<Auction>();
		try {
			if (reverse) {
				result.addAll(this.searchForAuctionType("reverse"));
			}
			if (dutch) {
				result.addAll(this.searchForAuctionType("dutch"));
			}
			if (dbl) {
				result.addAll(this.searchForAuctionType("double"));
			}
		} catch (IOException e) {
			AuctionHelper.error("was not able to search for auctions", this, e);
		} catch (ClassNotFoundException e) {
			AuctionHelper.error("was not to find class", this, e);
		}
		return result;
	}

	private ArrayList<Auction> searchForAuctionType(String type)
			throws IOException, ClassNotFoundException, ErrorResponseException {
		ArrayList<Auction> result = new ArrayList<Auction>();
		SearchAuctionsCommand cmd = new SearchAuctionsCommand(type, null,
				this.minPrice, this.maxPrice, this.requireTemplate);
		commandOutput.writeObject(cmd);
		Response answer = (Response) commandInput.readObject();
		if (answer instanceof AuctionSetResponse) {
			AuctionSetResponse asanswer = (AuctionSetResponse) answer;
			result.addAll(asanswer.getAuctions());
		} else if (answer instanceof ErrorResponse) {
			throw new ErrorResponseException(answer.getMessage());
		}
		return result;
	}

	private class ObjectInputStream extends java.io.ObjectInputStream implements
			Serializable {

		private static final long serialVersionUID = -3085583655867046032L;

		protected ObjectInputStream(InputStream in) throws IOException,
				SecurityException {
			super(in);
		}
	}

	private class ObjectOutputStream extends java.io.ObjectOutputStream
			implements Serializable {

		private static final long serialVersionUID = -7162264508073357801L;

		protected ObjectOutputStream(OutputStream out) throws IOException,
				SecurityException {
			super(out);
		}

	}

	private class Socket extends java.net.Socket implements Serializable {

		private static final long serialVersionUID = 9206444983034513030L;

		public Socket(String host, int port) throws UnknownHostException,
				IOException {
			super(host, port);
		}

	}

}
